package com.gitee.apanlh.util.algorithm.encrypt.asymmetric;

import com.gitee.apanlh.exp.algorithm.AsymmetricException;
import com.gitee.apanlh.util.algorithm.KeyUtils;
import com.gitee.apanlh.util.algorithm.encrypt.CipherMode;
import com.gitee.apanlh.util.base.ChooseOr;
import com.gitee.apanlh.util.base.Empty;
import com.gitee.apanlh.util.base.Eq;
import com.gitee.apanlh.util.encode.Base64Utils;
import com.gitee.apanlh.util.encode.HexUtils;
import com.gitee.apanlh.util.valid.Assert;
import com.gitee.apanlh.util.valid.ValidParam;
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * 	非对称算法加密/解密抽象类
 * 	<br>默认实现了加密和解密功能
 * 	<br>该抽象类提供了对称加密算法的通用操作和方法，子类可以继承此类并根据具体的非对称加密算法实现相应的加密和解密逻辑。
 * 	<br>该抽象类依赖于 Bouncy Castle提供的加密算法库，需要在使用前添加 Bouncy Castle作为加密算法提供者
 * 	<br>注意：该类的部分方法参数或字段可能需要根据具体的对称加密算法进行调整和适配
 * 	<br>参考文档：Bouncy Castle: <a href="https://www.bouncycastle.org/">...</a>
 *  TODO 区分BC以及JDK实现不同方式
 *  TODO BC是none 而JDK是 ECB
 *  @author Pan
 */
public abstract class BouncyCastleAsymmetricAbstract extends CustomAsymmetricAbstract implements AsymmetricEncode {
	
	/** TODO 未来需要改造兼容JDK及BC 静态Provider */
	Provider provider = null;
	
//	static {
//		CheckImport.library(CheckLibrary.BOUNCY_CASTLE);
//		bouncyCastleProvider = new BouncyCastleProvider();
//		Security.addProvider(bouncyCastleProvider);
//	}

	/** 算法类型 */
	private AsymmetricType asymmetricType;
	/** 算法名称 */
	private String algorithm;
	/** 加密器模式 */
	private CipherMode cipherMode;
	/** 加密器 */
	private Cipher encryptCipher;
	/** 解密器 */
	private Cipher decryptCipher;
	/** 公钥 */
	private PublicKey publicKey;
	/** 公钥字节 */
	private byte[] publicKeyBytes;
	/** 私钥 */
	private PrivateKey privateKey;
	/** 私钥字节 */
	private byte[] privateKeyBytes;
	/** 算法长度 */
	private int keyLength;
	/** 默认加密块大小 */
	private int encryptBlockSize = 117;
	/** 默认解密块大小 */
	private int decryptBlockSize = 128;

	/**	
	 * 	构造函数-初始化加解密器
	 * 	
	 * 	@author Pan
	 */
	protected BouncyCastleAsymmetricAbstract() {
		super();
	}
	
	/**	
	 * 	构造函数-初始化非对称加密算法
	 * 	<br>默认采用算法模式比如RSA、SM2等等
	 * 	<br>默认生成1024位密钥长度
	 * 	<br>动态加载加解密器模式（加密模式、解密模式）
	 * 	
	 * 	@author Pan
	 * 	@param 	asymmetricType	非对称加密算法类型
	 */
	protected BouncyCastleAsymmetricAbstract(AsymmetricType asymmetricType) {
		this.asymmetricType = asymmetricType;
		this.algorithm = asymmetricType.getAlgorithm();
		
		if (Eq.enums(AsymmetricType.RSA, asymmetricType)) {
			initKeyPair(1024);
		}
	}
	
	/**	
	 * 	构造函数-初始化非对称加密算法
	 * 	<br>自定义默认算法模式
	 * 	<br>如果字符串为hex或者base64编码格式将自动解码还原无需额外操作
	 * 	<br>如果只传递公钥可用于加密
	 * 	<br>如果只传递私钥可用于解密
	 * 	<br>动态加载加解密器模式（加密模式、解密模式）
	 * 
	 * 	@author Pan
	 * 	@param 	publicKey		公钥字符串
	 * 	@param 	privateKey 		私钥字符串
	 * 	@param 	asymmetricType	非对称加密算法类型
	 */
	protected BouncyCastleAsymmetricAbstract(String publicKey, String privateKey, AsymmetricType asymmetricType) {
		Assert.isFalse(ValidParam.isEmpty(publicKey) && ValidParam.isEmpty(privateKey), "string key is not allowed to be empty at the same time");
		
		this.asymmetricType = asymmetricType;
		this.algorithm = asymmetricType.getAlgorithm();

		if (ValidParam.isNotEmpty(publicKey)) {
			this.publicKeyBytes = KeyUtils.decode(publicKey);
		}
		if (ValidParam.isNotEmpty(privateKey)) {
			this.privateKeyBytes = KeyUtils.decode(privateKey);
		}
	}
	
	/**	
	 * 	构造函数-初始化非对称加密算法
	 * 	<br>如果只传递公钥可用于加密
	 * 	<br>如果只传递私钥可用于解密
	 * 	<br>动态加载加解密器模式（加密模式、解密模式）
	 * 	
	 * 	@author Pan
	 * 	@param 	publicKey	  	公钥
	 * 	@param 	privateKey	  	私钥
	 * 	@param 	asymmetricType	非对称加密算法类型
	 */
	protected BouncyCastleAsymmetricAbstract(byte[] publicKey, byte[] privateKey, AsymmetricType asymmetricType) {
		Assert.isFalse(ValidParam.isEmpty(publicKey) && ValidParam.isEmpty(privateKey), "key is not allowed to be empty at the same time");
		
		this.asymmetricType = asymmetricType;
		this.algorithm = asymmetricType.getAlgorithm();
		this.publicKeyBytes = publicKey;
		this.privateKeyBytes = privateKey;
	}
	
	/**	
	 * 	构造函数-初始化非对称加密算法
	 * 	<br>如果只传递公钥可用于加密
	 * 	<br>如果只传递私钥可用于解密
	 * 	<br>动态加载加解密器模式（加密模式、解密模式）
	 * 	
	 * 	@author Pan
	 * 	@param 	publicKey	  	公钥
	 * 	@param 	privateKey	  	私钥
	 * 	@param 	asymmetricType	非对称加密算法类型
	 */
	protected BouncyCastleAsymmetricAbstract(PublicKey publicKey, PrivateKey privateKey, AsymmetricType asymmetricType) {
		Assert.isFalse(ValidParam.isNotNull(publicKey) && ValidParam.isNotNull(privateKey), "key is not allowed to be empty at the same time");
		
		this.asymmetricType = asymmetricType;
		this.algorithm = asymmetricType.getAlgorithm();
		this.publicKey = publicKey;
		this.privateKey = privateKey;
	}
	
	/**	
	 * 	构造函数-根据公共指数及特征值初始化非对称加密算法
	 * 	<br>如果只传递公钥特征值可用于加密
	 * 	<br>如果只传递私钥特征值可用于解密
	 * 	<br>动态加载加解密器模式（加密模式、解密模式）
	 * 	
	 * 	@author Pan
	 * 	@param	modulus			公共指数
	 * 	@param 	publicExponent	公钥特征值
	 * 	@param 	privateExponent	私钥特征值
	 * 	@param 	asymmetricType	非对称加密算法类型
	 */
	protected BouncyCastleAsymmetricAbstract(BigInteger modulus, BigInteger publicExponent, BigInteger privateExponent, AsymmetricType asymmetricType) {
		Assert.isNotNull(modulus);
		Assert.isFalse(ValidParam.isNull(publicExponent) && ValidParam.isNull(privateExponent), "exponent is not allowed to be empty at the same time");
		
		this.asymmetricType = asymmetricType;
		this.algorithm = asymmetricType.getAlgorithm();
		
		if (ValidParam.isNotNull(publicExponent)) {
			this.publicKey = (PublicKey) getPublicKey(new RSAPublicKeySpec(modulus, publicExponent));
		} 
		if (ValidParam.isNotNull(privateExponent)) {
			this.privateKey = (PrivateKey) getPrivateKey(new RSAPrivateKeySpec(modulus, privateExponent));
		}
	}
	
	@Override
	public Cipher initCipher(byte[] publicKey, byte[] privateKey, CipherMode cipherMode) {
		try {
			Cipher cipher = null;
			if (this.provider == null) {
				cipher = Cipher.getInstance(getAlgorithm());
			} else {
				cipher = Cipher.getInstance(getAlgorithm(), getProvider());
			}
			cipher.init(cipherMode.getMode(), generateKey(publicKey, privateKey, cipherMode));
			return setCipher(cipherMode, cipher);
		} catch (Exception e) {
			throw new AsymmetricException("load algorithm:{}, {}", e, this.algorithm, e.getMessage());
		}
	}
	
	@Override
	public KeyPair initKeyPair(int keyLength) {
		KeyPair generateKeyPair = KeyUtils.generateKeyPair(getAlgorithm(), getProvider(), keyLength, new RSAKeyGenParameterSpec(keyLength, RSAKeyGenParameterSpec.F4));
		this.privateKey = generateKeyPair.getPrivate();
		this.publicKey = generateKeyPair.getPublic();
		this.keyLength = keyLength;
		return generateKeyPair;
	}
	
	@Override
	public byte[] doFinal(CipherMode cipherMode, byte[] content) {
		Cipher cipher = getCipher(cipherMode);
		try {
			int decryptBlockSize = getDecryptBlockSize();
			int encryptBlockSize = getEncryptBlockSize();

			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			boolean hasEncrypt = Eq.enums(CipherMode.ENCRYPT, cipherMode);
			int inputLen = content.length;
			int offSet = 0;
			byte[] buffer;
			int i = 0;

			// 数据分段加密/解密
			while (inputLen - offSet > 0) {
				if (inputLen - offSet > (hasEncrypt ? encryptBlockSize : decryptBlockSize)) {
					buffer = cipher.doFinal(content, offSet, (hasEncrypt ? encryptBlockSize : decryptBlockSize));
				} else {
					buffer = cipher.doFinal(content, offSet, inputLen - offSet);
				}
				baos.write(buffer, 0, buffer.length);
				i++;
				offSet = i * (hasEncrypt ? encryptBlockSize : decryptBlockSize);
			}
			return baos.toByteArray();
		} catch (Exception e) {
			throw new AsymmetricException(e.getMessage(), e);
		}
	}

	/**
	 * 	设置加解密器
	 * 	
	 * 	@author Pan
	 * 	@param 	cipherMode	加解密器模式
	 * 	@param 	cipher		加解密器
	 * 	@return Cipher
	 */
	private Cipher setCipher(CipherMode cipherMode, Cipher cipher) {
		if (Eq.enums(CipherMode.ENCRYPT, cipherMode)) {
			this.encryptCipher = cipher;
		} else {
			this.decryptCipher = cipher;
		}
		return cipher;
	}

	/**
	 * 	设置加密块大小
	 *
	 *	@author Pan
	 * 	@param 	size	块长度
	 * 	@return	int
	 */
	public int setEncryptBlockSize(int size) {
		return setBlockSize(size, CipherMode.ENCRYPT);
	}

	/**
	 * 	设置解密块大小
	 *
	 *	@author Pan
	 * 	@param 	size	块长度
	 * 	@return	int
	 */
	public int setDecryptBlockSize(int size) {
		return setBlockSize(size, CipherMode.DECRYPT);
	}

	/**
	 * 	设置加解密块大小
	 *
	 *	@author Pan
	 * 	@param 	size		块长度
	 * 	@param 	cipherMode	模式
	 * 	@return	int
	 */
	public int setBlockSize(int size, CipherMode cipherMode) {
		if (Eq.enums(CipherMode.ENCRYPT, cipherMode)) {
			this.encryptBlockSize = size;
		} else {
			this.decryptBlockSize = size;
		}
		return size;
	}

	/**	
	 * 	选择获取加解密器
	 * 	
	 * 	@author Pan
	 * 	@param 	cipherMode 加密器模式
	 * 	@return	Cipher
	 */
	private Cipher getCipher(CipherMode cipherMode) {
		if (Eq.enums(CipherMode.ENCRYPT, cipherMode)) {
			if (this.encryptCipher == null) {
				this.encryptCipher = initCipher(this.publicKeyBytes, this.privateKeyBytes, cipherMode);
			}
			return getEncryCipher();
		} 
		if (this.decryptCipher == null) {
			this.decryptCipher = initCipher(this.publicKeyBytes, this.privateKeyBytes, cipherMode);
		}
		return getDecryptCipher();
	}
	
	/**	
	 * 	获取加密器模式
	 * 
	 * 	@author Pan
	 * 	@return	CipherMode
	 */
	public CipherMode getCipherMode() {
		return cipherMode;
	}

	/**
	 * 	获取加密器
	 * 
	 * 	@author Pan
	 * 	@return	Cipher
	 */
	public Cipher getEncryCipher() {
		return this.encryptCipher;
	}
	
	/**
	 * 	获取解密器
	 * 
	 * 	@author Pan
	 * 	@return	Cipher
	 */
	public Cipher getDecryptCipher() {
		return this.decryptCipher;
	}
	
	/**	
	 * 	获取非对称算法类型
	 * 	
	 * 	@author Pan
	 * 	@return	AsymmetricType
	 */
	public AsymmetricType getAsymmetricType() {
		return asymmetricType;
	}
	
	/**
	 * 	获取算法名称
	 * 	
	 * 	@author Pan
	 * 	@return	String
	 */
	public String getAlgorithm() {
		return this.algorithm;
	}
	
	/**	
	 * 	获取算法长度
	 * 	
	 * 	@author Pan
	 * 	@return	int
	 */
	public int getKeyLength() {
		return keyLength;
	}

	@Override
	public PublicKey getPublicKey() {
		return this.publicKey;
	}
	
	@Override
	public PrivateKey getPrivateKey() {
		return this.privateKey;
	}

	/**
	 * 	获取加密块
	 *
	 * 	@return	int
	 */
	public int getEncryptBlockSize() {
		return this.encryptBlockSize;
	}

	/**
	 * 	获取解密块
	 *
	 * 	@return	int
	 */
	public int getDecryptBlockSize() {
		return this.decryptBlockSize;
	}

	@Override
	public Key getPublicKey(byte[] publicKey) {
		if (ValidParam.isNull(getPublicKey()) && !ValidParam.isEmpty(publicKey)) {
			this.publicKey = (PublicKey) getPublicKey(new X509EncodedKeySpec(publicKey));
		}
		return this.publicKey;
	}
	
	@Override
	public Key getPrivateKey(byte[] privateKey) {
		if (ValidParam.isNull(getPrivateKey()) && !ValidParam.isEmpty(privateKey)) {
			this.privateKey = (PrivateKey) getPrivateKey(new PKCS8EncodedKeySpec(privateKey));
		}
		return this.privateKey;
	}
	
	@Override
	public Key getPublicKey(KeySpec publicKey) {
		if (ValidParam.isNull(getPublicKey())) {
			this.publicKey = KeyUtils.generatePublicKey(getAlgorithm(), getProvider(), publicKey);
		}
		return this.publicKey;
	}
	
	@Override
	public Key getPrivateKey(KeySpec privateKey) {
		if (ValidParam.isNull(getPrivateKey())) {
			this.privateKey = KeyUtils.generatePrivateKey(getAlgorithm(), getProvider(), privateKey);
		}
		return this.privateKey;
	}
	
	@Override
	public Provider getProvider() {	
		return provider;
	}
	
	@Override
	public Key generateKey(byte[] publicKey, byte[] privateKey, CipherMode cipherMode) {
		return ChooseOr.create(Eq.enums(CipherMode.ENCRYPT, cipherMode), () -> getPublicKey(publicKey))
				.orElse(() -> getPrivateKey(privateKey));
	}
	
	@Override
	public Key generateKey(KeySpec publicKey, KeySpec privateKey, CipherMode cipherMode) {
		return ChooseOr.create(Eq.enums(CipherMode.ENCRYPT, cipherMode), () -> getPublicKey(publicKey))
				.orElse(() -> getPrivateKey(privateKey));
	}
	
	/**	
	 * 	获取公共指数
	 * 	
	 * 	@author Pan
	 * 	@return	BigInteger
	 */
	public BigInteger getModulus() {
		if (ValidParam.isNotNull(this.publicKey)) {
			return ((BCRSAPublicKey) this.publicKey).getModulus();
		}
		if (ValidParam.isNotNull(this.privateKey)) {
			return ((BCRSAPrivateKey) this.privateKey).getModulus();
		}
		return null;
	}
	
	/**	
	 * 	获取公钥特征值
	 * 	
	 * 	@author Pan
	 * 	@return	BigInteger
	 */
	public BigInteger getPublicExponent() {
		return this.publicKey == null ? null : ((BCRSAPublicKey) this.publicKey).getPublicExponent();
	}
	
	/**	
	 * 	获取私钥特征值
	 * 	
	 * 	@author Pan
	 * 	@return	BigInteger
	 */
	public BigInteger getPrivateExponent() {
		return this.privateKey == null ? null : ((BCRSAPrivateKey) this.privateKey).getPrivateExponent();
	}
	
	@Override
	public byte[] getPublicEncode() {
		return this.publicKey == null ? Empty.arrayByte() : getPublicKey().getEncoded();
	}

	@Override
	public String getPublicEncodeToHex() {
		byte[] publicEncode = getPublicEncode();
		return publicEncode == null ? null : HexUtils.encode(publicEncode);
	}

	@Override
	public byte[] getPublicEncodeToBase64() {
		byte[] publicEncode = getPublicEncode();
		return publicEncode == null ? Empty.arrayByte() : Base64Utils.encode(publicEncode);
	}

	@Override
	public String getPublicEncodeToBase64Str() {
		byte[] publicEncode = getPublicEncode();
		return publicEncode == null ? null : Base64Utils.encodeToStr(publicEncode);
	}

	@Override
	public byte[] getPrivateEncode() {
		return this.privateKey == null ? Empty.arrayByte() : getPrivateKey().getEncoded();
	}

	@Override
	public String getPrivateEncodeToHex() {
		return KeyUtils.encode(getPrivateEncode());
	}

	@Override
	public byte[] getPrivateEncodeToBase64() {
		byte[] privateEncode = getPrivateEncode();
		return privateEncode == null ? Empty.arrayByte() : Base64Utils.encode(privateEncode);
	}

	@Override
	public String getPrivateEncodeToBase64Str() {
		byte[] privateEncode = getPrivateEncode();
		return privateEncode == null ? null : Base64Utils.encodeToStr(privateEncode);
	}
	
	/**	
	 * 	创建Key密钥对对象(可用于缓存)
	 * 	<br>默认十六进制编码格式
	 * 
	 * 	@author Pan
	 * 	@return	AsymmetricKey
	 */
	public AsymmetricKey createKeyObject() {
		return createKeyObject(true);
	}
	
	/**	
	 * 	创建Key密钥对对象(可用于缓存)
	 * 	<br>自定义编码格式(true十六进制, false Base64)
	 * 
	 * 	@author Pan
	 * 	@param 	isHex	true十六进制, false Base64
	 * 	@return	AsymmetricKey
	 */
	public AsymmetricKey createKeyObject(boolean isHex) {
		return new AsymmetricKey(getPublicEncode(), getPrivateEncode(), isHex);
	}
}
